#include "Application.h"
#include <string>
#include "../SDLtoolkit/widgets/PlayScenarioPanel.h"
#include "../gui/WindowHelper.h"
#include "../common/DTOShipStatus.h"
#include "../SDLtoolkit/widgets/Ship.h"
#include "../common/DTOShipStatus.h"
#include "../common/DTOCliente.h"
#include "../common/utils/GameUtils.h"

Application::Application() {
	Logger::log("Application", "Application", "Iniciando Aplicación");
	this->initializeSDL();
	game = NULL;
	this->server = NULL;
	this->cliente = NULL;
}

Application::~Application() {
	if (this->activeWindow != NULL){
		delete activeWindow;
		this->activeWindow = NULL;
	}

	//TODO tratar de eliminar estas ventanas
	//Por el momento no puedo porque alguna de estas no
	//esta en memoria, al hacer switch una ventana se borra
	//delete createWindow;
	//delete connectWindow;

	if (this->game != NULL){
		delete this->game;
		this->game = NULL;
	}

	if (this->engine != NULL){
		delete this->engine;
		this->engine = NULL;
	}

	Logger::log("Application", "Application", "Finalizando Aplicación");
}

void Application::switchActiveWindow(WindowID id, std::string param) {
	Window* aux = NULL;

	switch (id) {
	case load:
		aux = this->activeWindow;
		this->activeWindow = WindowHelper::connectGameWindow();
		delete aux;
		break;
	case play:
		aux = this->activeWindow;
		this->activeWindow = WindowHelper::playSceneWindow(game);
		delete aux;
		break;
	case EXIT:
		aux = this->activeWindow;
		this->activeWindow = WindowHelper::exitWindow();
		delete aux;

		break;

	default:
		Logger::log("Application", "switchActiveWindow","Se desecho un evento.");
		break;
	}

	//Si entro aca mando un resize, si no hace falta no pasa nada..
	SDL_Event event2;
	event2.type = SDL_VIDEORESIZE;
	SDL_PushEvent(&event2);
}

void Application::initializeSDL() {
	this->engine = new SDLWrapper("Inicio", GAME_MENU_WIDTH, GAME_MENU_HEIGHT, BITS_PER_PIXEL,Color::GRIS);
	this->engine->start();
}

void* Application::run() {
	//TODO parche, removerlo.
	Logger::init();


	Timer fps;
	bool quit = false;

	//Create the play scene
	//SpaceBattleInterface* game = new SpaceBattleInterface(MAIN_SCENARIO,true);
	createWindow = WindowHelper::createGameWindow();
	connectWindow = WindowHelper::connectGameWindow();
	this->activeWindow = connectWindow;

	DTOCliente* dtoCliente =NULL;

	Event* event = NULL;
	while (quit == false) {

		fps.start();
		while (this->engine->pullEvent(&event)) {

			if(this->cliente != NULL){
				Logger::log("Application","run","Soy cliente : " + this->cliente->getClienteName() + " . Evento Recibido : " + event->getTypeDescription());
			}

			switch (event->getType()) {

			Logger::log("Application", "run","Evento del tipo: " + event->getTypeDescription());


			case UserEvent:

				switch (event->getEvent()->user.code) {
					case Ship_Status:
						if(this->activeWindow->getWindowType() == play){
							PlayScenarioPanel* play = (PlayScenarioPanel*)(this->activeWindow->getScenarioContainer());

							//Actualizo el estado del ship en el escenario panel
							DTOShipStatus* dtoStatusShip = (DTOShipStatus*)Translator::translate(*event);
							ShipStatus* shipStatus = new ShipStatus(dtoStatusShip);
							play->addPlayer(shipStatus);

						}

						break;
					case NewUser:{

						if(this->activeWindow->getWindowType() != play){
							std::string newClientName = *((std::string*)event->getEvent()->user.data1);
							std::string newClientShip = *((std::string*)event->getEvent()->user.data2);

							//PARCHE
							event->setSender(newClientName);

							std::stringstream ss;
							ss << "Sender: " << event->getSender();

							Logger::log("application", "run", &ss);

							this->switchActiveWindow(play, STRING_EMPTY);
							this->engine->changeScreenTitle("Jugando");


							//Agrego el nuevo cliente conectado

							PlayScenarioPanel* play = (PlayScenarioPanel*)(this->activeWindow->getScenarioContainer());

							DTOShipStatus* newClientShipStatus = new DTOShipStatus(newClientShip,newClientName);
							play->addPlayer(new ShipStatus(newClientShipStatus));

							//Envio mi estado
							dtoCliente = new DTOCliente(this->cliente->getClienteName(),CONNECTED,play->getShipStatus().transformInDTO());

							this->cliente->setMensaje(dtoCliente);

							delete newClientShipStatus;
						}
					}
					break;
					case UserSyncronize:{
						//Envio mi estado
						if(this->activeWindow->getWindowType() == play){
							PlayScenarioPanel* play = (PlayScenarioPanel*)(this->activeWindow->getScenarioContainer());

							this->cliente->setMensaje((DTO*)play->getShipStatus().transformInDTO());
						}
					}break;
					case UserDisconected:{

						// Termina cliente
						if (this->cliente != NULL) {
							this->cliente->terminar();

							this->cliente->join();
						}

						game->resetClientInterface();

						SDL_Event sdl_event;
						//Hago un push del evento - load.
						sdl_event.type = SDL_USEREVENT;
						sdl_event.user.code = load;

						SDL_PushEvent(&sdl_event);

					}break;
				}
				handleUserEvent(*event);
				this->activeWindow->handleEvent(*event);

				break;
			case Resize:
				this->engine->updateVideoMode(this->activeWindow->getWidth(), this->activeWindow->getHeight(),this->activeWindow->getBgrColor());
				break;
			case Quit:

				quit = true;

				if (this->server != NULL) {
					this->server->terminar();
					this->server->join();
					this->server = NULL;
				}
				break;
			case KeyReleased:
				if(event->getKey() == Escape){
					SDL_Event event1;
					event1.type = SDL_USEREVENT;
					event1.user.code = UserDisconected;
					SDL_PushEvent(&event1);

					break;
				}

			case KeyPressed:

				//Solo me interezan los eventos del play windows, el resto no.
				if (game != NULL && activeWindow->getWindowType() == play) {

					//Verify the status of the conection si valid
					if(!this->cliente->esValida()){
						SDL_Event event1;
						event1.type = SDL_USEREVENT;
						event1.user.code = UserDisconected;
						SDL_PushEvent(&event1);
					}

					//Propaga los eventos a los widgets de la ventana
					event->setSender(this->cliente->getClienteName());
					this->cliente->setMensaje(Translator::translate(*event));

				}
				this->activeWindow->handleEvent(*event);
				break;
			default:
				//TODO tipo que puede caer aca?
				this->activeWindow->handleEvent(*event);

				break;
			}
		}
		this->engine->clearScreen();

		this->activeWindow->resolve();

		this->activeWindow->showOn(this->engine->getMainSurface()->getSurface());

		this->engine->refreshScreen();

        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }

        //Free memory
        if(event != NULL){
        	delete event;
        	//Avoid double free
        	event = NULL;
        }

	}

	terminar();

	return NULL;

}

//TODO a una factory afuera
void Application::handleUserEvent(Event& event) {
	std::string screenTitle;
	std::string host;
	std::string shipName;
	int separador;
	std::string port;
	SocketProtocoloPlano* socket;
	std::string user;
	ShipStatus* localShip;
	SDL_Event sdl_event;

	//TODO esto es feo, ver como mejorar
	switch (event.getCode()) {
		case CREATE:
			//Puede definir de donde vienen las conexiones, puerto
			port = event.getData();
			user = event.getData2();
			engine->setPlayerName(user);

			break;
		case CONNECT: {

			//Hago un push del evento - conectando.
			sdl_event.type = SDL_USEREVENT;
			sdl_event.user.code = CLIENT_CONNECTING;

			SDL_PushEvent(&sdl_event);

			//Puede definir de donde vienen las conexiones, ip y puerto
			//Formato esperado= IP:PUERTO
			separador = event.getData().find_first_of(':');
			host = event.getData().substr(0, separador);
			port = event.getData().substr(separador + 1);

			//Formato esperado= usuarioName:shipname
			separador = event.getData2().find_first_of(':');
			user = event.getData2().substr(0, separador);
			shipName = event.getData2().substr(separador + 1);
			engine->setPlayerName(user);

			break;
		}
		default:
			//No hacer naranja
			break;
	}

	switch (event.getCode()) {
		case load:
			this->switchActiveWindow(event.getCode(), STRING_EMPTY);
			this->engine->changeScreenTitle("Inicio");
			break;
		case PRINCIPAL:
			this->switchActiveWindow(event.getCode(), STRING_EMPTY);
			this->engine->changeScreenTitle("Start Game");
			break;
		case connection:
			this->switchActiveWindow(event.getCode(), STRING_EMPTY);
			this->engine->changeScreenTitle("Connection Game");
			break;
		case CREATE:

			//Avoid to have multiple instance of server running in the same machine
			if ((this->server != NULL
					&& this->server->isRunning())
					|| (Socket::checkPortAvailability(UtilString::stringToInt(port)) != SOCKET_EXITO)) {

				//Hago un push del evento - error al crear server.
				sdl_event.type = SDL_USEREVENT;
				sdl_event.user.code = SERVER_ERROR;

				SDL_PushEvent(&sdl_event);

				Logger::log("Application", "handleUserEvent",
						"Otra instancia de server esta corriendo.");
				break;
			}

			game = new SpaceBattleInterface(event.getData2(), true);
			this->server = new Servidor(game);

			//Inicio el listener
			server->iniciar(UtilString::stringToInt(port), SERVER_MAX_QUEUE);

			//Inicio el server que replica los mensajes
			server->start();

			//Hago un push del evento - creación del server.
			sdl_event.type = SDL_USEREVENT;
			sdl_event.user.code = SERVER_CREATED;

			SDL_PushEvent(&sdl_event);

			Logger::log("Application", "handleUserEvent", "Server iniciado");

			break;
		case CONNECT:
			socket = new SocketProtocoloPlano();
			if (socket->connect(host, UtilString::stringToInt(port)) == 0) {

				//En la misma instancia puede tener un servidor y un cliente
				if (game == NULL) {
					game = new SpaceBattleInterface(user);
				}

				//Set the ship selected from the menu
				localShip = Persistor::getShipByName(shipName);
				//Parche
				localShip->setOwner(user);


				this->game->setShipStatus(localShip);

				cliente = new Client(user, host, game, socket);

				if(cliente->isConnected()){
					cliente->start();

					//Start the seed
					srand(time(0));

					//Set the initial postition for the ship
					int xPosition = GameUtils::getRandValueInRange(game->getWidth());
					int yPosition = GameUtils::getRandValueInRange(game->getHeight());

					game->getShip()->updatePosition(xPosition,yPosition);

					//Hago un push del evento - conexión del cliente.
					sdl_event.type = SDL_USEREVENT;
					sdl_event.user.code = CLIENT_CREATED;

					SDL_PushEvent(&sdl_event);

					Logger::log("Application", "handleUserEvent", "Conexion iniciada");
				}else{
					delete cliente;
					cliente = NULL;

					//Hago un push del evento - conexión del cliente.
					sdl_event.type = SDL_USEREVENT;
					sdl_event.user.code = CLIENT_ERROR;

					SDL_PushEvent(&sdl_event);

					Logger::log("Application", "handleUserEvent", "Fallo la Conexion");
				}
			} else {

				//Hago un push del evento - error en conexión del cliente.
				SDL_Event event;
				event.type = SDL_USEREVENT;
				event.user.code = CLIENT_ERROR;

				SDL_PushEvent(&event);

				Logger::log("Application", "handleUserEvent", "Fallo la Conexion");
			}
			break;

		case UserKeyPressed:
		case UserKeyReleased:
			//Solo me interezan los eventos del play windows, el resto no.
			if (game != NULL && activeWindow->getWindowType() == play) {

				Logger::log("Application", "run","Evento del tipo : " + event.getTypeDescription());
				activeWindow->handleEvent(event);
			}
			break;
		case TABCLIENT:
			//Solo me interezan los eventos del play windows, el resto no.
			if (activeWindow->getWindowType() == CREATE) {
				this->activeWindow = connectWindow;
			}
			break;
		case TABSERVER:
			if (activeWindow->getWindowType() == CONNECT) {
				this->activeWindow = createWindow;			}
			break;
		default:
			break;
	}
}

void Application::terminar() {

	Logger::finish();

	//Pongo la pantalla de cerrando.
	if (this->activeWindow != NULL){
		delete activeWindow;
		this->activeWindow = NULL;
	}

	this->activeWindow = WindowHelper::exitWindow();
	this->engine->refreshScreen();

	if (this->activeWindow != NULL){
		delete activeWindow;
		this->activeWindow = NULL;
	}

	this->engine->stop();
}

